/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CommandHandler;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import main.Bot;
import main.CommandSettings;
import main.CommonMethods;
import main.Constants;
import main.Main;
import main.UserCentral;
import main.UserCentral.UserPrefixes;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import output.OutputQueue;
import botobjects.Admin;
import botobjects.Army;
import botobjects.Quote;

import com.google.api.search.Search;
import com.google.api.translate.Language;
import com.google.api.translate.Translate;

import database.DBConnectionFactory;

/**
 * A class that takes care of functions related to the Bot
 * 
 * 
 */
public class BotHandler extends CommandHandler {
   private static BotHandler INSTANCE = null;

   /**
    * Checks if anyone was waiting for the specified user. If so, it messages the people who were waiting
    */
   public static void checkForWait(final String nick) {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM wait WHERE nick = ?");
		 ps.setString(1, UserCentral.getInstance().getOnlineUser(nick));
		 rs = ps.executeQuery();
		 while (rs.next()) {
			for (String user : UserCentral.getInstance().getCurrentNicks(rs.getString("waiter"))) {
			   Bot.getInstance().sendMessage(user, rs.getString("nick") + " is here now, using the nick " + nick);
			}
		 }
		 removeWait("", UserCentral.getInstance().getOnlineUser(nick));
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   public static final BotHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new BotHandler();
	  }
	  return INSTANCE;
   }

   public static final BotHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   /**
    * Removes a wait
    * 
    * @param onlineUser
    * @param userFromDB
    * @return
    */
   public static boolean removeWait(final String onlineUser, final String userFromDB) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 if ("".equals(onlineUser)) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("DELETE FROM wait WHERE nick = ?");
			ps.setString(1, userFromDB);
		 } else {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("DELETE FROM wait WHERE nick LIKE ? AND waiter = ?");
			ps.setString(1, userFromDB);
			ps.setString(2, onlineUser);
		 }
		 if (ps.executeUpdate() >= 1) {
			out = true;
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Updates the silence list when someone changes nick
    * 
    * @param oldNick
    * @param newNick
    */
   public static void updateSilenceList(final String oldNick, final String newNick) {
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("UPDATE silencelist SET nick = ? WHERE nick = ?");
		 ps.setString(1, newNick);
		 ps.setString(2, oldNick);
		 ps.executeUpdate();
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   /**
    * Updates the silence list when someone changes prefix
    * 
    * @param nick
    */
   public static void updateSilenceListPrefix(final String nick) {
	  PreparedStatement ps = null;
	  UserPrefixes up = UserCentral.getInstance().getUserPrefix(nick);
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("UPDATE silencelist SET prefix = ? WHERE nick = ?");
		 ps.setString(
			   1,
			   ((up.isOwner ? "q" : "") + (up.isSop ? "a" : "") + (up.isOp ? "o" : "") + (up.isHalfOp ? "h" : "") + (up.hasVoice ? "v"
					 : "")));
		 ps.setString(2, nick);
		 ps.executeUpdate();
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   private BotHandler() {
   }

   /**
    * Saves the silence information
    * 
    * @param silenceMap
    */
   private void addSilenceList(final Map<String, String> silenceMap) {
	  PreparedStatement ps = null;
	  try {
		 for (final Entry<String, String> e : silenceMap.entrySet()) {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("INSERT INTO silencelist VALUES(?,?)");
			ps.setString(1, e.getKey());
			ps.setString(2, e.getValue());
			ps.executeUpdate();
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   /**
    * Adds a wait
    * 
    * @param onlineUser
    * @param userFromDB
    * @return
    */
   private boolean addWait(final String onlineUser, final String userFromDB) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("INSERT INTO wait VALUES(?,?)");
		 ps.setString(1, userFromDB);
		 ps.setString(2, onlineUser);
		 if (ps.executeUpdate() == 1) {
			out = true;
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Clears the silence list
    */
   private void clearSilenceList() {
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("DELETE FROM silencelist");
		 ps.executeUpdate();
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
   }

   /**
    * Creates silence in the main channel and saves the prefixes users had before the silence, so they can be restored later
    * 
    * @param command
    *           .getSender()
    */
   private void createSilence() {
	  clearSilenceList();
	  Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), "+m");
	  String mode = "-";
	  String nicks = "";
	  int i = 0;
	  Map<String, String> silenceMap = new HashMap<String, String>();
	  for (final String user : UserCentral.getInstance().getMainchanUsers()) {
		 UserPrefixes up = UserCentral.getInstance().getUserPrefix(user);
		 if (i >= 12) {
			Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), mode + nicks);
			mode = "-";
			nicks = "";
			i = 0;
		 }
		 if (!up.isOwner && !up.isSop && !UserCentral.getInstance().onlineUserIsAdmin(user, "")
			   && !user.equals(Bot.getInstance().getNick()) && !Bot.getInstance().workersContain(user)) {
			if (up.isOp) {
			   mode += "o";
			   nicks += " " + user;
			   ++i;
			   silenceMap.put(user, "o");
			}
			if (up.isHalfOp) {
			   mode += "h";
			   nicks += " " + user;
			   ++i;
			   if (!silenceMap.containsKey(user)) {
				  silenceMap.put(user, "h");
			   }
			}
			if (up.hasVoice) {
			   mode += "v";
			   nicks += " " + user;
			   ++i;
			   if (!silenceMap.containsKey(user)) {
				  silenceMap.put(user, "v");
			   }
			}
		 }
	  }
	  addSilenceList(silenceMap);
	  if (i > 0) {
		 Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), mode + nicks);
	  }
	  Bot.getInstance().sendMessage(Main.getBotSettings().getMainChannel(), Colors.RED + "Stfu and listen");
   }

   /**
    * Removes the special hourly message
    * 
    * @return
    */
   private List<String> delSpecialHourlyMessage() {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("DELETE FROM hourlymessage WHERE id = 0");
		 if (ps.executeUpdate() == 1) {
			out.add("Hourly message removed");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Returns the map of users and what prefixes they had before the silence was initiated
    * 
    * @return
    */
   private Map<String, String> getSilenceMap() {
	  Map<String, String> out = new HashMap<String, String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM silencelist");
		 rs = ps.executeQuery();
		 while (rs.next()) {
			out.put(rs.getString("nick"), rs.getString("prefix"));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput output) {
   }

   public void handleAddadmin(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() > 1) {
		 String user = CommonMethods.getUserFromDB(command.get(1), false);
		 if ("".equals(user)) {
			out.addOutput("No such user");
		 } else if (new Admin(user, false).saveToDB()) {
			out.addOutput("Admin added successfully");
		 }
	  }
   }

   public void handleAddquote(final IRCCommand command, final IRCOutput out) {
	  if (command.size() >= 2) {
		 Quote quote = new Quote(0, command.getFullMessage(), UserCentral.getInstance().getOnlineUser(command.getSender()),
			   GregorianCalendar.getInstance().getTimeInMillis());
		 if (quote.saveToDB()) {
			out.addOutput("Quote added with ID " + quote.getId());
		 }
	  }
   }

   public void handleAdmins(final IRCCommand command, final IRCOutput out) {
	  out.addOutput(listAdmins());
   }

   public void handleCancel(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 2 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			&& command.get(1).equalsIgnoreCase("all")) {
		 OutputQueue.INSTANCE.cancelOutput(command.get(1).toLowerCase());
	  } else if (command.size() == 2 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			&& !"".equals(UserCentral.getInstance().getOnlineUser(command.get(1)))) {
		 OutputQueue.INSTANCE.cancelOutput(UserCentral.getInstance().getOnlineUser(command.get(1)));
	  } else if (command.size() == 1) {
		 OutputQueue.INSTANCE.cancelOutput(UserCentral.getInstance().getOnlineUser(command.getSender()));
	  }
   }

   public void handleGoogle(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 1) {
		 Search.setHttpReferrer("http://www.lucidessence.com/lucidbot/");
		 try {
			out.addOutput(Search.execute(command.getFullMessage()));
		 } catch (Exception e) {
			Logger.getLogger("Error").error("", e);
		 }
	  }
   }

   public void handleLock(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 Bot.getInstance().setAdminLock(true);
		 out.addOutput("Bot has now been locked");
		 Bot.getInstance().sendMessage(
			   Main.getBotSettings().getMainChannel(),
			   Colors.RED + "Bot has been locked by an admin. No commands can be used until it's unlocked again. "
					 + "Attacks, spells etc. can still be posted, however");
	  }
   }

   public void handleQuit(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 Bot.getInstance().disconnect();
		 try {
			DBConnectionFactory.getConnectionHandler().getConnection().close();
		 } catch (final SQLException e) {
			Logger.getLogger("Error").error("", e);
		 }
		 System.exit(0);
	  }
   }

   public void handleQuote(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 Quote q = Quote.getQuote(-1);
		 if (q != null) {
			out.addOutput(q.getIRCOutput(0));
		 }
	  } else if (command.isInt(1)) {
		 Quote q = Quote.getQuote(Integer.parseInt(command.get(1)));
		 if (q != null) {
			out.addOutput(q.getIRCOutput(0));
		 }
	  }
   }

   public void handleReload(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1 && UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 CommandSettings.INSTANCE.reload();
		 out.addOutput("Reloaded");
	  }
   }

   public void handleRemoveadmin(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() > 1) {
		 String user = CommonMethods.getUserFromDB(command.get(1), false);
		 if ("".equals(user)) {
			out.addOutput("No such admin");
		 } else if (Admin.deleteFromDB(user)) {
			out.addOutput("Admin removed successfully");
		 }
	  }
   }

   public void handleRemovehourlymessage(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 out.addOutput(delSpecialHourlyMessage());
	  }
   }

   public void handleRemovequote(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 2 && command.isInt(1)) {
		 if (Quote.deleteFromDB(command.get(1))) {
			out.addOutput("Quote removed");
		 }
	  }
   }

   public void handleRemovewait(final IRCCommand command, final IRCOutput out) {
	  if (command.size() > 1) {
		 String user = UserCentral.getInstance().getOnlineUser(command.getSender());
		 List<String> waitingFor = new LinkedList<String>();
		 if (command.getFullMessage().equalsIgnoreCase("all")) {
			if (removeWait(user, "%")) {
			   out.addOutput("Cleared wait list");
			}
		 } else {
			for (String nick : command.getFullMessage().split(" ")) {
			   String waitfor = CommonMethods.getUserFromDB(nick, false);
			   if (!"".equals(waitfor)) {
				  waitingFor.add(waitfor);
			   }
			}
			if (!waitingFor.isEmpty()) {
			   StringBuilder results = new StringBuilder(50);
			   for (String s : waitingFor) {
				  if (removeWait(user, s)) {
					 results.append(s).append(" ");
				  }
			   }
			   out.addOutput("Removed waiting for: " + results.toString());
			}
		 }
	  }
   }

   public void handleSetapikey(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())
			&& command.getSource() == Constants.PM_SOURCE && setGoogleAPIKey(command.size() == 1 ? "" : command.get(1))) {
		 out.addOutput(command.size() == 1 ? "Google API Key removed" : "Google API Key set");
	  }
   }

   public void handleSethourlymessage(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel()) && command.size() > 1) {
		 out.addOutput(setSpecialHourlyMessage(command.getFullMessage()));
	  }
   }

   public void handleSilence(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 if (command.size() == 1) {
			createSilence();
		 } else if (command.get(1).equals("reset")) {
			undoSilence();
		 }
	  }
   }

   public void handleTranslate(final IRCCommand command, final IRCOutput out) {
	  Translate.setHttpReferrer("http://www.lucidessence.com/lucidbot/");
	  String from = "";
	  String to = "";
	  if (command.size() > 3 && Language.fromString(command.get(1).toUpperCase()) != null) {
		 if (Language.fromString(command.get(2).toUpperCase()) == null) {
			to = Language.fromString(command.get(1).toUpperCase()).toString();
		 } else {
			from = Language.fromString(command.get(1).toUpperCase()).toString();
			to = Language.fromString(command.get(2).toUpperCase()).toString();
		 }

		 if (!"".equals(from) && command.size() < 4)
			return;
		 int index = "".equals(to) ? 2 : 3;
		 try {
			out.addOutput(Translate.execute(command.getPartialMessage(index, command.size()), from, to));
		 } catch (Exception e) {
			Logger.getLogger("Error").error("", e);
		 }
	  }
   }

   public void handleUnlock(final IRCCommand command, final IRCOutput out) {
	  if (UserCentral.getInstance().onlineUserIsAdmin(command.getSender(), command.getChannel())) {
		 Bot.getInstance().setAdminLock(false);
		 out.addOutput("Bot has now been unlocked");
		 Bot.getInstance().sendMessage(Main.getBotSettings().getMainChannel(),
			   Colors.RED + "Bot has been unlocked and can now be used again");
	  }
   }

   public void handleVersion(final IRCCommand command, final IRCOutput out) {
	  out.addOutput("Bot version: " + Main.getBotSettings().getReleaseVersion());
   }

   public void handleWait(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 out.addOutput(listWait(UserCentral.getInstance().getOnlineUser(command.getSender())));
	  } else if (command.size() > 1) {
		 String user = UserCentral.getInstance().getOnlineUser(command.getSender());
		 List<String> waitingFor = new LinkedList<String>();
		 if (command.getFullMessage().equalsIgnoreCase("armies home")) {
			for (Army army : Army.getArmies(Constants.ARMY_GET_ALL_HOME)) {
			   if (!user.equals(army.getMainNick())) {
				  waitingFor.add(army.getMainNick());
			   }
			}
		 } else {
			for (String nick : command.getFullMessage().split(" ")) {
			   String waitfor = CommonMethods.getUserFromDB(nick, false);
			   if (!"".equals(waitfor) && !user.equals(waitfor)) {
				  waitingFor.add(waitfor);
			   }
			}
		 }
		 if (!waitingFor.isEmpty()) {
			StringBuilder results = new StringBuilder(50);
			for (String s : waitingFor) {
			   if (addWait(user, s)) {
				  results.append(s).append(" ");
			   }
			}
			out.addOutput("Added waiting for: " + results.toString());
		 }
	  }
   }

   /**
    * Lists all admins
    * 
    * @return
    */
   private List<String> listAdmins() {
	  List<String> out = new LinkedList<String>();

	  String output = "Admins are: ";
	  for (Admin admin : Admin.getAdmins()) {
		 if (output.length() > 350) {
			out.add(output);
			output = "";
		 }
		 output = output + admin.getNick() + ", ";
	  }
	  if (output.length() > 2) {
		 out.add(output.substring(0, output.length() - 2));
	  }
	  return out;
   }

   /**
    * Lists which users the calling user is waiting for
    * 
    * @param onlineUser
    * @return
    */
   private List<String> listWait(final String onlineUser) {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("SELECT * FROM wait WHERE waiter = ?");
		 ps.setString(1, onlineUser);
		 rs = ps.executeQuery();
		 String output = "";
		 while (rs.next()) {
			if (output.length() > 350) {
			   out.add(output.substring(0, output.length() - 2));
			   output = "";
			}
			output += rs.getString("nick") + ", ";
		 }
		 if (output.length() > 1) {
			out.add(output.substring(0, output.length() - 2));
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Sets the google API Key
    */
   private boolean setGoogleAPIKey(final String newKey) {
	  boolean out = false;
	  PreparedStatement ps = null;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("DELETE FROM google");
		 ps.executeUpdate();

		 try {
			ps.close();
		 } catch (final Exception e) {
		 }

		 ps = DBConnectionFactory.getConnectionHandler().getConnection().prepareStatement("INSERT INTO google VALUES(?)");
		 ps.setString(1, newKey);
		 if (ps.executeUpdate() == 1) {
			out = true;
			Main.getBotSettings().setGoogleAPIKey(newKey);
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Sets a message that is repeated each hour change
    * 
    * @param message
    * @return
    */
   private List<String> setSpecialHourlyMessage(final String message) {
	  List<String> out = new LinkedList<String>();
	  PreparedStatement ps = null;
	  try {
		 delSpecialHourlyMessage();

		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("INSERT INTO hourlymessage VALUES(0,?)");
		 ps.setString(1, message);
		 if (ps.executeUpdate() == 1) {
			out.add("Hourly message set");
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   /**
    * Resets user prefixes and remove the +m, as to undo the silence
    */
   private void undoSilence() {
	  Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), "-m");
	  String mode = "+";
	  String nicks = "";
	  int i = 0;
	  for (final Entry<String, String> e : getSilenceMap().entrySet()) {
		 String prefix = e.getValue();
		 if (i >= 12) {
			Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), mode + nicks);
			mode = "+";
			nicks = "";
			i = 0;
		 }

		 mode += prefix;
		 nicks += " " + e.getKey();
		 ++i;
	  }
	  if (i > 0) {
		 Bot.getInstance().setMode(Main.getBotSettings().getMainChannel(), mode + nicks);
	  }
	  clearSilenceList();
   }
}
